{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Kaggle cifar10 图像目标检测实战"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "2.0.0\n",
      "sys.version_info(major=3, minor=6, micro=10, releaselevel='final', serial=0)\n",
      "matplotlib 3.1.2\n",
      "numpy 1.18.1\n",
      "pandas 0.25.3\n",
      "sklearn 0.22.1\n",
      "tensorflow 2.0.0\n",
      "tensorflow_core.keras 2.2.4-tf\n"
     ]
    }
   ],
   "source": [
    "import matplotlib as mpl\n",
    "import matplotlib.pyplot as plt\n",
    "%matplotlib inline\n",
    "import numpy as np\n",
    "import sklearn\n",
    "import pandas as pd\n",
    "import os\n",
    "import sys\n",
    "import time\n",
    "import tensorflow as tf\n",
    "from tensorflow import keras\n",
    "\n",
    "print(tf.__version__)\n",
    "print(sys.version_info)\n",
    "for module in mpl,np,pd,sklearn,tf,keras:\n",
    "    print(module.__name__,module.__version__)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "1 Physical GPUs, 1 Logical GPUs\n"
     ]
    }
   ],
   "source": [
    "def solve_cudnn_error():\n",
    "    gpus = tf.config.experimental.list_physical_devices('GPU')\n",
    "    if gpus:\n",
    "        try:\n",
    "            # Currently, memory growth needs to be the same across GPUs\n",
    "            for gpu in gpus:\n",
    "                tf.config.experimental.set_memory_growth(gpu, True)\n",
    "            logical_gpus = tf.config.experimental.list_logical_devices('GPU')\n",
    "            print(len(gpus), \"Physical GPUs,\", len(logical_gpus), \"Logical GPUs\")\n",
    "        except RuntimeError as e:\n",
    "            # Memory growth must be set before GPUs have been initialized\n",
    "            print(e)\n",
    "\n",
    "solve_cudnn_error()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 数据读取"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [],
   "source": [
    "class_names = [\n",
    "    'airplane',\n",
    "    'automobile',\n",
    "    'bird',\n",
    "    'cat',\n",
    "    'deer',\n",
    "    'dog',\n",
    "    'frog',\n",
    "    'horse',\n",
    "    'ship',\n",
    "    'truck'\n",
    "]\n",
    "\n",
    "train_labels_file = './data/cifar10/trainLabels.csv'\n",
    "test_csv_file = './data/cifar10/sampleSubmission.csv'\n",
    "train_folder = './data/cifar10/train'\n",
    "test_folder = './data/cifar10/test'"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[('./data/cifar10/train\\\\1.png', 'frog'),\n",
      " ('./data/cifar10/train\\\\2.png', 'truck'),\n",
      " ('./data/cifar10/train\\\\3.png', 'truck'),\n",
      " ('./data/cifar10/train\\\\4.png', 'deer'),\n",
      " ('./data/cifar10/train\\\\5.png', 'automobile')]\n",
      "[('./data/cifar10/test\\\\1.png', 'cat'),\n",
      " ('./data/cifar10/test\\\\2.png', 'cat'),\n",
      " ('./data/cifar10/test\\\\3.png', 'cat'),\n",
      " ('./data/cifar10/test\\\\4.png', 'cat'),\n",
      " ('./data/cifar10/test\\\\5.png', 'cat')]\n",
      "50000 300000\n"
     ]
    }
   ],
   "source": [
    "# 解析csv，构成(filename(path), label)形式\n",
    "def parse_csv_file(filepath, folder):\n",
    "    \"\"\"Parses csv files into (filename(path), label) format\"\"\"\n",
    "    results = []\n",
    "    with open(filepath, 'r') as f:\n",
    "        lines = f.readlines()[1:]\n",
    "    for line in lines:\n",
    "        image_id, label_str = line.strip('\\n').split(',')\n",
    "        image_full_path = os.path.join(folder, image_id + '.png')\n",
    "        results.append((image_full_path, label_str))\n",
    "    return results\n",
    "\n",
    "train_labels_info = parse_csv_file(train_labels_file, train_folder)\n",
    "test_csv_info = parse_csv_file(test_csv_file, test_folder)\n",
    "\n",
    "import pprint\n",
    "pprint.pprint(train_labels_info[0:5])\n",
    "pprint.pprint(test_csv_info[0:5])\n",
    "print(len(train_labels_info), len(test_csv_info))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "                     filepath       class\n",
      "0  ./data/cifar10/train\\1.png        frog\n",
      "1  ./data/cifar10/train\\2.png       truck\n",
      "2  ./data/cifar10/train\\3.png       truck\n",
      "3  ./data/cifar10/train\\4.png        deer\n",
      "4  ./data/cifar10/train\\5.png  automobile\n",
      "                         filepath       class\n",
      "0  ./data/cifar10/train\\45001.png       horse\n",
      "1  ./data/cifar10/train\\45002.png  automobile\n",
      "2  ./data/cifar10/train\\45003.png        deer\n",
      "3  ./data/cifar10/train\\45004.png  automobile\n",
      "4  ./data/cifar10/train\\45005.png    airplane\n",
      "                    filepath class\n",
      "0  ./data/cifar10/test\\1.png   cat\n",
      "1  ./data/cifar10/test\\2.png   cat\n",
      "2  ./data/cifar10/test\\3.png   cat\n",
      "3  ./data/cifar10/test\\4.png   cat\n",
      "4  ./data/cifar10/test\\5.png   cat\n"
     ]
    }
   ],
   "source": [
    "# 把训练集和测试集转换成DataFrame\n",
    "train_df = pd.DataFrame(train_labels_info[0:45000])\n",
    "valid_df = pd.DataFrame(train_labels_info[45000:])\n",
    "test_df = pd.DataFrame(test_csv_info)\n",
    "\n",
    "train_df.columns = ['filepath', 'class']\n",
    "valid_df.columns = ['filepath', 'class']\n",
    "test_df.columns = ['filepath', 'class']\n",
    "\n",
    "print(train_df.head())\n",
    "print(valid_df.head())\n",
    "print(test_df.head())"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 数据增强"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Found 45000 validated image filenames belonging to 10 classes.\n",
      "Found 5000 validated image filenames belonging to 10 classes.\n",
      "45000 5000\n"
     ]
    }
   ],
   "source": [
    "# 将图片转化成同一尺寸\n",
    "height = 32\n",
    "width = 32\n",
    "channels = 3\n",
    "batch_size = 32\n",
    "num_classes = 10\n",
    "\n",
    "# 读取图片并增强\n",
    "train_datagen = keras.preprocessing.image.ImageDataGenerator(\n",
    "    rescale= 1./255, # 缩放到0-1之间\n",
    "    rotation_range= 40, # 旋转范围\n",
    "    width_shift_range= 0.2, # 水平位移\n",
    "    height_shift_range= 0.2, # 竖直平移\n",
    "    shear_range= 0.2, # 剪切范围\n",
    "    zoom_range= 0.2, # 缩放范围\n",
    "    horizontal_flip= True, # 随机水平翻转\n",
    "    fill_mode= 'nearest', # 对空白位置的填充规则\n",
    ")\n",
    "# 按照DataFrame进行分类，进行数据增强\n",
    "train_generator = train_datagen.flow_from_dataframe(\n",
    "    train_df,\n",
    "    directory = './',\n",
    "    x_col = 'filepath',\n",
    "    y_col = 'class',\n",
    "    classes = class_names,\n",
    "    target_size = (height, width),\n",
    "    batch_size = batch_size,\n",
    "    seed = 7,\n",
    "    shuffle = True,\n",
    "    class_mode = 'sparse'\n",
    ")\n",
    "\n",
    "valid_datagen = keras.preprocessing.image.ImageDataGenerator(\n",
    "    rescale= 1./255, # 缩放到0-1之间\n",
    ")\n",
    "valid_generator = valid_datagen.flow_from_dataframe(\n",
    "    valid_df,\n",
    "    directory = './',\n",
    "    x_col = 'filepath',\n",
    "    y_col = 'class',\n",
    "    classes = class_names,\n",
    "    target_size = (height, width),\n",
    "    batch_size = batch_size,\n",
    "    seed = 7,\n",
    "    shuffle = False,\n",
    "    class_mode = 'sparse'\n",
    ")\n",
    "\n",
    "train_num = train_generator.samples\n",
    "valid_num = valid_generator.samples\n",
    "print(train_num, valid_num)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "(32, 32, 32, 3) (32,)\n",
      "[2. 1. 4. 4. 4. 4. 6. 5. 2. 8. 4. 6. 6. 3. 7. 1. 7. 2. 8. 8. 3. 0. 5. 3.\n",
      " 9. 1. 4. 5. 6. 7. 9. 2.]\n"
     ]
    }
   ],
   "source": [
    "for i in range(1):\n",
    "    x, y = train_generator.next()\n",
    "    print(x.shape, y.shape)\n",
    "    print(y)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 模型构建"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Model: \"sequential\"\n",
      "_________________________________________________________________\n",
      "Layer (type)                 Output Shape              Param #   \n",
      "=================================================================\n",
      "conv2d (Conv2D)              (None, 32, 32, 128)       3584      \n",
      "_________________________________________________________________\n",
      "conv2d_1 (Conv2D)            (None, 32, 32, 128)       147584    \n",
      "_________________________________________________________________\n",
      "max_pooling2d (MaxPooling2D) (None, 16, 16, 128)       0         \n",
      "_________________________________________________________________\n",
      "conv2d_2 (Conv2D)            (None, 16, 16, 256)       295168    \n",
      "_________________________________________________________________\n",
      "conv2d_3 (Conv2D)            (None, 16, 16, 256)       590080    \n",
      "_________________________________________________________________\n",
      "max_pooling2d_1 (MaxPooling2 (None, 8, 8, 256)         0         \n",
      "_________________________________________________________________\n",
      "conv2d_4 (Conv2D)            (None, 8, 8, 512)         1180160   \n",
      "_________________________________________________________________\n",
      "conv2d_5 (Conv2D)            (None, 8, 8, 512)         2359808   \n",
      "_________________________________________________________________\n",
      "max_pooling2d_2 (MaxPooling2 (None, 4, 4, 512)         0         \n",
      "_________________________________________________________________\n",
      "flatten (Flatten)            (None, 8192)              0         \n",
      "_________________________________________________________________\n",
      "dense (Dense)                (None, 512)               4194816   \n",
      "_________________________________________________________________\n",
      "dense_1 (Dense)              (None, 10)                5130      \n",
      "=================================================================\n",
      "Total params: 8,776,330\n",
      "Trainable params: 8,776,330\n",
      "Non-trainable params: 0\n",
      "_________________________________________________________________\n"
     ]
    }
   ],
   "source": [
    "model = keras.models.Sequential([\n",
    "    keras.layers.Conv2D(filters=128, kernel_size=3, padding='same', activation='relu',\n",
    "                        input_shape=[width, height, channels]),\n",
    "#     keras.layers.BatchNormalization(),\n",
    "    keras.layers.Conv2D(filters=128, kernel_size=3, padding='same', activation='relu'),\n",
    "#     keras.layers.BatchNormalization(),\n",
    "    keras.layers.MaxPool2D(pool_size=2),\n",
    "    \n",
    "    keras.layers.Conv2D(filters=256, kernel_size=3, padding='same', activation='relu'),\n",
    "#     keras.layers.BatchNormalization(),\n",
    "    keras.layers.Conv2D(filters=256, kernel_size=3, padding='same', activation='relu'),\n",
    "#     keras.layers.BatchNormalization(),\n",
    "    keras.layers.MaxPool2D(pool_size=2),\n",
    "    \n",
    "    keras.layers.Conv2D(filters=512, kernel_size=3, padding='same', activation='relu'),\n",
    "#     keras.layers.BatchNormalization(),\n",
    "    keras.layers.Conv2D(filters=512, kernel_size=3, padding='same', activation='relu'),\n",
    "#     keras.layers.BatchNormalization(),\n",
    "    keras.layers.MaxPool2D(pool_size=2),\n",
    "    \n",
    "    keras.layers.Flatten(),\n",
    "    keras.layers.Dense(512, activation='relu'),\n",
    "    keras.layers.Dense(num_classes, activation='softmax'),\n",
    "])\n",
    "\n",
    "model.compile(loss='sparse_categorical_crossentropy',\n",
    "              optimizer='adam', metrics=['accuracy'])\n",
    "model.summary()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Epoch 1/20\n",
      "1406/1406 [==============================] - 254s 180ms/step - loss: 1.8813 - accuracy: 0.3018 - val_loss: 1.5018 - val_accuracy: 0.4477\n",
      "Epoch 2/20\n",
      "1406/1406 [==============================] - 249s 177ms/step - loss: 1.5755 - accuracy: 0.4215 - val_loss: 1.3958 - val_accuracy: 0.4866\n",
      "Epoch 3/20\n",
      "1406/1406 [==============================] - 249s 177ms/step - loss: 1.4164 - accuracy: 0.4854 - val_loss: 1.1644 - val_accuracy: 0.5895\n",
      "Epoch 4/20\n",
      "1406/1406 [==============================] - 248s 177ms/step - loss: 1.3142 - accuracy: 0.5248 - val_loss: 1.1422 - val_accuracy: 0.5863\n",
      "Epoch 5/20\n",
      "1406/1406 [==============================] - 249s 177ms/step - loss: 1.2299 - accuracy: 0.5619 - val_loss: 1.0191 - val_accuracy: 0.6326\n",
      "Epoch 6/20\n",
      "1406/1406 [==============================] - 249s 177ms/step - loss: 1.1615 - accuracy: 0.5859 - val_loss: 1.1024 - val_accuracy: 0.6182\n",
      "Epoch 7/20\n",
      "1406/1406 [==============================] - 249s 177ms/step - loss: 1.1169 - accuracy: 0.6038 - val_loss: 0.9839 - val_accuracy: 0.6512\n",
      "Epoch 8/20\n",
      "1406/1406 [==============================] - 249s 177ms/step - loss: 1.0756 - accuracy: 0.6208 - val_loss: 0.8926 - val_accuracy: 0.6799\n",
      "Epoch 9/20\n",
      "1406/1406 [==============================] - 249s 177ms/step - loss: 1.0391 - accuracy: 0.6341 - val_loss: 0.9022 - val_accuracy: 0.6891\n",
      "Epoch 10/20\n",
      "1406/1406 [==============================] - 249s 177ms/step - loss: 1.0103 - accuracy: 0.6455 - val_loss: 0.9140 - val_accuracy: 0.6893\n",
      "Epoch 11/20\n",
      "1406/1406 [==============================] - 248s 177ms/step - loss: 0.9845 - accuracy: 0.6541 - val_loss: 0.9877 - val_accuracy: 0.6741\n"
     ]
    }
   ],
   "source": [
    "# 训练\n",
    "epochs = 20\n",
    "# 定义回调函数\n",
    "callbacks = [\n",
    "#     keras.callbacks.TensorBoard(log_dir=logdir,profile_batch = 100000000),\n",
    "#     keras.callbacks.ModelCheckpoint(output_model_file,save_best_only=True),\n",
    "    keras.callbacks.EarlyStopping(patience=3, min_delta=1e-3),\n",
    "]\n",
    "\n",
    "history = model.fit_generator(train_generator,\n",
    "                              steps_per_epoch= train_num // batch_size,\n",
    "                              epochs=epochs,\n",
    "                              validation_data=valid_generator,\n",
    "                              validation_steps= valid_num // batch_size,\n",
    "                              callbacks = callbacks)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "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\n",
      "text/plain": [
       "<Figure size 576x360 with 1 Axes>"
      ]
     },
     "metadata": {
      "needs_background": "light"
     },
     "output_type": "display_data"
    },
    {
     "data": {
      "image/png": "iVBORw0KGgoAAAANSUhEUgAAAfUAAAEzCAYAAAAo+Zq4AAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAALEgAACxIB0t1+/AAAADh0RVh0U29mdHdhcmUAbWF0cGxvdGxpYiB2ZXJzaW9uMy4xLjIsIGh0dHA6Ly9tYXRwbG90bGliLm9yZy8li6FKAAAgAElEQVR4nO3deXxU1f3/8dcnGwGyQCDsO4Sg7LKrQBAExH1piwtuRaRVWtu625+132+rVaq2Koi4FK0L8lVoURFxIQKyiCAIyBYRMCyyCSRAWM/vjztojAkZyEwmc/N+Ph7zmJl7z733c7hJ3tw7d8415xwiIiIS/WIiXYCIiIiEhkJdRETEJxTqIiIiPqFQFxER8QmFuoiIiE8o1EVERHyi1FA3s8ZmNtPMVprZCjP7bTFtzMyeMLMcM/vCzM4oNG+wma0OzLs71B0QERERTzBH6keAPzjnTgN6AreY2elF2pwHZAQeI4CnAcwsFhgTmH86cGUxy4qIiEgIlBrqzrktzrnFgdd5wEqgYZFmFwMvOc98oIaZ1Qe6AznOuXXOuUPAxEBbERERCbGT+kzdzJoBnYEFRWY1BL4p9D43MK2k6SIiIhJiccE2NLMk4E3gNufc3qKzi1nEnWB6cesfgXfqnsTExC5NmjQJtrSocuzYMWJi/Ht9ovoX3dS/6OXnvoH/+7dmzZodzrn0sq4nqFA3s3i8QH/FOTe5mCa5QONC7xsBm4GEEqb/hHNuPDAeIDMz061evTqY0qJOdnY2WVlZkS4jbNS/6Kb+RS8/9w383z8z2xCK9QRz9bsBzwMrnXOPldBsKnBt4Cr4nsAe59wWYCGQYWbNzSwBGBpoKyIiIiEWzJH6WcAwYJmZLQlMuxdoAuCcGwdMA4YAOcB+4IbAvCNmdivwHhALvOCcWxHSHoiIiAgQRKg75+ZQ/Gfjhds44JYS5k3DC30REREJo6AvlBMRESnJ4cOHyc3NpaCgICzrT01NZeXKlWFZd3lKTEykUaNGxMfHh2X9CnURESmz3NxckpOTadasGd6lWKGVl5dHcnJyyNdbnpxz7Ny5k9zcXJo3bx6Wbfj3+wEiIlJuCgoKqFWrVlgC3S/MjFq1aoXtbAYo1EVEJEQU6KUL97+RQl1ERHwhKSkp0iVEnEJdRETEJxTqIiLiK8457rjjDtq1a0f79u15/fXXAdiyZQt9+vShU6dOtGvXjtmzZ3P06FGuv/7679s+/vjjEa6+bHT1u4iI+MrkyZNZsmQJS5cuZceOHXTr1o0+ffrw6quvMmjQIO677z6OHj3K/v37WbJkCZs2bWL58uUA7N69O8LVl41CXUREQurPb63gy81F7/tVNhm1q/KXyzsF1XbOnDlceeWVxMbGUrduXfr27cvChQvp1q0bN954I4cPH+aSSy6hU6dOtGjRgnXr1jFq1CjOP/98Bg4cGNK6y5tOv4uIiK94g5z+VJ8+fZg1axYNGzZk2LBhvPTSS9SsWZOlS5eSlZXFmDFjGD58eDlXG1oV8kj9yLFIVyAiIqfqTxe2Dfk68/Lygm7bp08fnnnmGa677jp27drFrFmzGD16NBs2bKBhw4bcdNNN7Nu3j8WLFzNkyBASEhK4/PLLadmyJddff33Iay9PFTLUdx8s/n9ZIiIipbn00kuZN28eHTt2xMx45JFHqFevHi+++CKjR48mPj6epKQkXnrpJTZt2sQNN9zAsWPe0eRDDz0U4erLpkKGev5hx6qte2lTLyXSpYiISJTIz88HvAFeRo8ezejRo380/7rrruO66677yXKLFy8ul/rKQ4X8TD0GGD19daTLEBERiSoVMtRTqxgfrtrGp1/vinQpIiIiUaNChnpKFaNuShX+9u7KEq9iFBERkR+rkKFuwG0DWrN4425mfPltpMsRERGJChUy1AF+1qURLdKrM/q91Rw5qu+4iYiIlKbChnpcbAx3DmpDzrZ83lycG+lyREREKrwKG+oAg9rWpXOTGjz+/loKDh+NdDkiIiIVWoUOdTPjrsFt2Lq3gAlz10e6HBER8YkT3Xt9/fr1tGvXrhyrCZ0KHeoAPVvUol9mOmNn5rBn/+FIlyMiIlJhVfhQB7hzcBvyDh5h7Mc5kS5FREQqoLvuuouxY8d+//6BBx7gz3/+M/379+eMM86gffv2/Pe//z3p9RYUFHDDDTfQvn17OnfuzMyZMwFYsWIF3bt3p1OnTnTo0IG1a9eyb98+zj//fDp27Ei7du2+v497eaqQw8QWdVr9FC7t1JAJn6zn+jObUT+1aqRLEhGRkrx7N2xdFtJVVqmVCRc9VuL8oUOHctttt/HrX/8agEmTJjF9+nR+97vfkZKSwo4dO+jZsycXXXQRZhb0dseMGQPAsmXLWLVqFQMHDmTNmjWMGzeO3/72t1x99dUcOnSIo0ePMm3aNBo0aMA777wDwJ49e8rQ41MTFUfqAL87tzXOwT/eXxvpUkREpILp3Lkz27ZtY/PmzSxdupSaNWtSv3597r33Xjp06MCAAQPYtGkT3357cmOfzJkzh2HDhgHQpk0bmjZtypo1a+jVqxcPPvggDz/8MBs2bKBq1aq0b9+eDz74gLvuuovZs2eTmpoajq6eUKlH6mb2AnABsM0595MrB8zsDuDqQus7DUh3zu0ys/VAHnAUOOKc63qqhTZOq8awXk351ydfM7x3czLqJp/qqkREJJzO+1vIV3kwL4+EUtpcccUVvPHGG2zdupWhQ4fyyiuvsH37dhYtWkR8fDzNmjWjoKDgpLZb0qimV111FT169OCdd95h0KBBPPfcc5xzzjksWrSIadOmcc899zBw4EDuv//+k9peWQVzpD4BGFzSTOfcaOdcJ+dcJ+Ae4GPnXOFB2/sF5p9yoB93S79WVE+IY/R7utmLiIj82NChQ5k4cSJvvPEGV1xxBXv27KFOnTrEx8czc+ZMNmzYcNLr7NOnD6+88goAa9asYePGjWRmZrJu3TpatGjBb37zGy666CK++OILNm/eTLVq1bjmmmu4/fbbI3L3t1KP1J1zs8ysWZDruxJ4rSwFnUha9QRu7tuCv89Yw6INu+jSNC1cmxIRkSjTtm1b8vLyaNiwIfXr1+fqq6/mwgsvpGvXrnTq1Ik2bdqc9Dp//etfM3LkSNq3b09cXBwTJkygSpUqvP7667z88svEx8dTr1497r//fhYuXMgdd9xBTEwM8fHxPP3002Ho5YmF7EI5M6uGd0R/a6HJDphhZg54xjk3vqzbufHs5rw4bwMPv7ua12/ueVIXPIiIiL8tW/bDBXq1a9dm3rx5xbY7fu/14jRr1ozly5cDkJiYyIQJE37S5p577uGee+750bRBgwYxaNCgU6g6dCyYu6AFjtTfLu4z9UJtfgFc45y7sNC0Bs65zWZWB3gfGOWcm1XC8iOAEQDp6eldJk2aVGI9H208zEtfHuK2M6rQqU5UXMD/vfz8/BMOehDt1L/opv5Fr0j3LTU1lVatWoVt/UePHiU2NjZs6y9POTk5P7kyvl+/fotC8TF1KBNxKEVOvTvnNgeet5nZFKA7UGyoB47ixwNkZma6rKysEjd01tFjzH58FtM2GaOu6ENsTPQcrWdnZ3OivkU79S+6qX/RK9J9W7lyJcnJ4buAOS8vL+TrX7Zs2fdXth9XpUoVFixYENLtFJWYmEjnzp3Dsu6QhLqZpQJ9gWsKTasOxDjn8gKvBwL/E4rtxcfGcPvATG55dTFTPt/EFV0ahWK1IiJSibRv354lS5ZEuoyQKvXqdzN7DZgHZJpZrpn90sxGmtnIQs0uBWY45/YVmlYXmGNmS4FPgXecc9NDVfiQ9vXo0CiVx2as1s1eREQqgGA+zq3swv1vFMzV71cG0WYC3lffCk9bB3Q81cJKY2bcPbgNVz23gJfnb2B47xbh2pSIiJQiMTGRnTt3UqtWLV3AXALnHDt37iQxMTFs24iuq8yKOLNVbfq0TuepmTn8vFtjUhLjI12SiEil1KhRI3Jzc9m+fXtY1l9QUBDWMCwviYmJNGoUvo+MozrUAe4clMkFT87hmY+/4o5BJ/8dRBERKbv4+HiaN28etvVnZ2eH7eIyP4masd9L0q5hKhd3asDzc77m270nN/yfiIiIn0R9qAP84dxMjh5z/OMD3exFREQqL1+EepNa1bi6R1MmffYNX20veZQgERERP/NFqAPcek4rEuNi+Ltu9iIiIpWUb0K9dlIVburTgneXb+Xzjd9FuhwREZFy55tQBxjeuwW1kxJ4ePoqDYIgIiKVjq9CPalKHKPOyWD+ul18vCY835UUERGpqHwV6gBXdm9Ck7RqPDx9NceO6WhdREQqD9+FekJcDH8Y2JqVW/YydenmSJcjIiJSbnwX6gAXdmhA2wYp/H3Gag4e0c1eRESkcvBlqMfEGHcNbkPudwd4Zf7GSJcjIiJSLnwZ6gC9M2pzVqtaPDUzh7yCw5EuR0REJOx8G+pm3tH6rn2HeHbWukiXIyIiEna+DXWADo1qcH6H+jw352u25elmLyIi4m++DnWA2wdmcujIMZ78MCfSpYiIiISV70O9ee3qDO3emNc+3cj6HfsiXY6IiEjY+D7UAX7TP4P42Bhu/7+l5B88EulyREREwqJShHqd5ERG/6wDn3+zm2HPL2DPAV0NLyIi/lMpQh3ggg4NGHPVGSzftIdrnlvA7v2HIl2SiIhISFWaUAcY3K4e467pwuqteVz57AJ25h+MdEkiIiIhU6lCHaD/aXV57rqurNuez9Dx8/VVNxER8Y1KF+oAfVqnM+GG7mzafYChz8xny54DkS5JRESkzCplqAP0almLl27szra8g/z8mXl8s2t/pEsSEREpk1JD3cxeMLNtZra8hPlZZrbHzJYEHvcXmjfYzFabWY6Z3R3KwkOha7M0Xh7egz37DzN0/Hx9j11ERKJaMEfqE4DBpbSZ7ZzrFHj8D4CZxQJjgPOA04Erzez0shQbDp0a1+DVm3qy/9ARfjF+Hjnb8iNdkoiIyCkpNdSdc7OAXaew7u5AjnNunXPuEDARuPgU1hN27RqmMnFEL44ecwwdP4/VW/MiXZKIiMhJC9Vn6r3MbKmZvWtmbQPTGgLfFGqTG5hWqsSC7VCwN0SlBSezXjITR/Qixoyh4+exfNOect2+iIhIWZlzrvRGZs2At51z7YqZlwIcc87lm9kQ4J/OuQwz+xkwyDk3PNBuGNDdOTeqhG2MAEYAdKkf22XOqCaszfgVO2t3O8WunZpv9x3j4YUFFBxx3N4tkRapsSFdf35+PklJSSFdZ0Wi/kU39S96+blv4P/+9evXb5FzrmtZ1xNX1hU45/YWej3NzMaaWW28I/PGhZo2AjafYD3jgfEAp7dq6hJT69J++V+g3eUw+GFISi9rqUE7s9d+rnpuPo8tPsyEGzrTtVlayNadnZ1NVlZWyNZX0ah/0U39i15+7hv4v3+hUubT72ZWz8ws8Lp7YJ07gYVAhpk1N7MEYCgwNZh1Ho1NhBEfQ7/74MupMKYbLJ0IQZxVCIXGadWYdHMv0pOrcO0LnzLvq53lsl0REZGyCOYrba8B84BMM8s1s1+a2UgzGxlocgWw3MyWAk8AQ53nCHAr8B6wEpjknFsRdGVxCdD3Thg5B2plwJSb4ZUrYPfGk+ziqamfWpXXR/SkYY2qXP+vT5m1Znu5bFdERORUBXP1+5XOufrOuXjnXCPn3PPOuXHOuXGB+U8559o65zo653o65+YWWnaac661c66lc+6vp1RhnTZw43Q47xHYMA/G9IQFz8Cxo6e0upPadEoiE0f0pEV6EsNf/IwPV34b9m2KiIicqugYUS4mFnrcDLfMhyY94d074YXBsG1V2DddK6kKr93Ug8x6yYx8eRHTl28N+zZFRERORXSE+nE1msA1b8Klz8DOtfBMb/j4ETgS3tuo1qiWwCs39aBdw1RueXUxby0t8Xo/ERGRiImuUAcwg45D4ZaFcNqFMPOvML4v5C4K62ZTEuP59y970KVJTX478XPeXJQb1u2JiIicrOgL9eOS0uGKF+DKiXBgNzw/AKbfC4fCN357UpU4JtzYjV4ta3H7G0t57dPyuWhPREQkGNEb6sdlnud91t7lepg/Bsb2gq9mhm1z1RLieP66bvRtnc49k5fx4tz1YduWiIjIyYj+UAdITIULHofr34GYOPj3JfCfW+DAd+HZXHwszwzrwrmn1+VPU1cwNjuHY8fK5zv0IiIiJfFHqB/X7Gz41Sdw9u9g6WvwVHf48r9h2VSVuFjGXn0G53eozyPTV/OL8fNY861uBCMiIpHjr1AHiK8KAx6AETMhuR5MuhYmXg15of8qWnxsDE9d2ZlHruhAzrZ8hvxzNg9PX8WBQ+H/Dr2IiEhR/gv14+p3hJtmegGf84F31L7qnZBvxsz4edfGfPiHLC7p3JCns79i4D8+ZubqbSHfloiIyIn4N9QBYuO8U/G/mgtpzeHNm2DnV2HZVFr1BP7+s45MHNGThNgYbvjXQm55ZTHf7i0Iy/ZERESK8neoH1erJQx9xQv5ySPg6OGwbapni1pM+21v/nBua95f+S0DHv2YF+eu56gupBMRkTCrHKEOkNoILvgHbPrMG4UujKrExTKqfwYzbutDpyY1+NPUFVw69hOWb9oT1u2KiEjlVnlCHaDdZdDxKpj9d+/mMGHWrHZ1XrqxO09c2ZnNuwu46Kk5vLryIPkHj4R92yIiUvlUrlAHOO9hbwz5KSOgIPxHzmbGRR0b8OEf+nJVjya8v+EIAx79mOnLt+DK6f7wIiJSOVS+UE9MgcuehT2bYNod5bbZ1Krx/OWS9vyxZyI1qycw8uXFDH/xM3K/219uNYiIiL9VvlAHaNwd+t4JX7wOy94o1023rBHLW7eexX1DTmPeup2c+9gsnvn4Kw4fPVaudYiIiP9UzlAH6H07NOoOb/8edpfvjVniYmO4qU8L3v99X87OqM1D767iwifnsGhDeIa1FRGRyqHyhnpsHFw2HtwxmHwzHCv/UeAa1qjKs9d2ZfywLuw9cJjLn57LPZOXsWd/+L5yJyIi/lV5Qx28AWmGjIaNc2HO4xErY2Dberz/+74MP7s5kz77hv6PZfOfzzfpQjoRETkplTvUAToOhbaXQfZDsGlRxMqoXiWOP15wOlNvPYuGNatx2+tLuOb5BXy5eW/EahIRkeiiUDeDCx6DpHreMLIH8yNaTtsGqUz+1Zn87yXtWJa7hyFPzOaWVxeTs013gBMRkRNTqANUrQmXjoNd6+C9eyJdDbExxrCeTZl95zmMOqcV2au2MfDxWfz+9SVs2Lkv0uWJiEgFpVA/rnlvOPs2WPwSrHwr0tUAkFotnj8MzGTWnf0Y3rsF7yzbwjmPfsw9k79g0+4DkS5PREQqGIV6YVn3erdsnToK9m6JdDXfq5VUhXuHnMbsO/sxrGdT3ly0iX6js3lg6gq26S5wIiISoFAvLC4BLn8eDhfAf0bCsYo1IEydlEQeuKgtM+/I4vIuDfn3/A30GT2TB6etZNe+Q5EuT0REIqzUUDezF8xsm5ktL2H+1Wb2ReAx18w6Fpq33syWmdkSM/sslIWHTe0MGPwQrMuGBU9HuppiNaxRlYcu68CHv+/LkHb1eXb2Ono//BGPzljNngP6jruISGUVzJH6BGDwCeZ/DfR1znUA/hcYX2R+P+dcJ+dc11MrMQK6XA+Z58MHD8DWZZGupkTNalfnsV90YsZtfcjKrMOTH+XQ++GPePLDtboTnIhIJVRqqDvnZgG7TjB/rnPu+Pim84FGIaotcszgoie9q+LfHA6HK/ZFaRl1kxlz9Rm885uz6d68Fo++v4beD3/E+FlfceBQ+Y+UJyIikRHqz9R/Cbxb6L0DZpjZIjMbEeJthVf1WnDJWNi+Ct7/U6SrCUrbBqk8d11X/nPLWbRrmMqD01bRZ/RMXpy7noNHFO4iIn5nwQxFambNgLedc+1O0KYfMBY42zm3MzCtgXNus5nVAd4HRgWO/ItbfgQwAiA9Pb3LpEmTTrIr4dEy5zka577FF+3/H7tqlf0ThPz8fJKSkkJQWelW7zrK5LWHWP3dMdISjYtbxnNWwzjiYixs2yzP/kWC+hfd/Nw/P/cN/N+/fv36LQrFx9QhCXUz6wBMAc5zzq0poc0DQL5z7u+lbS8zM9OtXr261LrKxeECePYc2LcNfjUPktLLtLrs7GyysrJCU1sQnHN8krOTv89YzZJvdtMkrRq3Dcjg4k4NiQ1DuJd3/8qb+hfd/Nw/P/cN/N8/MwtJqJf59LuZNQEmA8MKB7qZVTez5OOvgYFAsVfQV2jxiXD5s1CwF6beClF2kxUz4+yM2kz59Zk8f11XkqrE8ftJS+k7eiZPZ3/FzvyDkS5RRERCJK60Bmb2GpAF1DazXOBPQDyAc24ccD9QCxhrZgBHAv/bqAtMCUyLA151zk0PQx/Cr25bOPd/YPpd8Nnz0G14pCs6aWZG/9Pq0i+zDjO+3MqEuet5ePoqHn9/DRd0qM81vZrSuXENAvtLRESiUKmh7py7spT5w4GfpJxzbh3Q8adLRKkeN8PaGfDefdCsN6RnRrqiUxITYwxuV5/B7eqz5ts8Xp6/gcmLNzH58020bZDCtb2aclHHhlRNiI10qSIicpI0olywzLyr4ROqw5u/hCPRf9q6dd1k/ufidsy/tz9/uaQdR4467npzGT0e/ID/fftL1m2P7B3rRETk5CjUT0ZyPbjoKW9Amo/+EulqQiapShzX9GzK9Nt6M+nmXvRpnc6Lc9dzzqMfM+z5BcxYsZUjRyvWkLkiIvJTpZ5+lyLaDIEuN8DcJ6HVAGjRN9IVhYyZ0b15Gt2bp7Etr4DXP/2GVz/dyIh/L6JBaiJX92zKz7s2Jj25SqRLFRGRYuhI/VQM+ivUagVTRsL+Egfbi2p1khMZ1T+D2Xf2Y9w1XWieXp3R763mzL99yG9e+5zP1u8imK9DiohI+dGR+qlIqA6XPwfPDYC3fgs/f8n7zN2H4mJjGNyuHoPb1SNnWz6vLNjAG4tymbp0M23qJTOsV1Mu6dSQ6lX0oyQiEmk6Uj9VDTrBOX+ElVNh4XPeIDU+16pOEn+6sC0L7u3PQ5e1x8y4b8pyej74IQ9MXUHONl1YJyISSTq8KoszR0HOBzDtdph2B6Q0gJrNoGZzSAs812wOac29m8P45Gi+WkIcV3ZvwtBujVm88Tv+PW8Dry7YyIS562ldM4YVLofeGbVp1yCVmDAOSSsiIj+mUC+LmFi4ciKsnga7vobv1sN3X3tBn7/1x22rpEDNZpx+NAkOfxQI/GZe4Kc0gtjo2xVmRpemaXRpmsYfLzjI6wu/YdK8tYx+bzWj31tNzWrxnNWqNn0y0jk7ozYNalSNdMkiIr4WfUlS0VRJgg4//+n0Q/th94Yfh/1360nKXQHzP4Ojh35oGxMHqY29gC8c9rUyvEFuouAIv3ZSFW7p14q2lku7rr34JGcHs9bsYPba7bz9xRbAO33fO8ML+R4t0qiWoB8/EZFQ0l/VcEmoBnVO8x6FfJqdTVaf3pC3JRD4gdA/Hv6bJ8OB735YoMNQOP9R7z8PUaJ2UhUu7tSQizs1xDnHmm/zmb12O7PW7uDVBRv51yfriY81ujZNo3fr2vRulU7bBik6VS8iUkYK9UiIiYXURt6jee+fzj+w2wv4Ve/A7L/D5sXwsxeh7unlXmpZmRmZ9ZLJrJfM8N4tKDh8lM/Wf/d9yD8yfTWPsJq06gmc1ao2vTO8R/1UnaoXETlZCvWKqGoNqNrJu8K+eW94c7h3+9chj0DnYVFxOr4kifGxnJ1Rm7MzanMPsC2vgE9ydjB7zQ5mrd3BW0s3A5BRJ4neGen0bl2bHs11ql5EJBj6S1nRNe8DI+fA5Jtg6ihYPwfOfyyqTsefSJ3kRC7t3IhLOzfCOceqrXnMXrud2Wt38PKCDbzwydckxMbQpWlNerWsRc8WtejYOJUqcbrhjIhIUQr1aJBUB66ZDLMfheyHYNNi+PmL3i1hfcTMOK1+CqfVT2FEn5YUHD7KwvW7mL12B7PX7uCx99cAUCXOC/kezWvRs0UanZrUUMiLiKBQjx4xsdD3TmjSy7tL3LPnwJDRFed0/P5dMH8sbVYvgq5tISm9zKtMjI/1TsFneOv6bt8hPl2/iwXrdjF/3U7+8eEa3AdeyJ/RpCY9WqTRs0UtOjWuQWK8Ql5EKh+FerRp3rtinY4/sBvmj4X5T8PBvdSxOBh3FlzyNLTqH9JN1ayewKC29RjUth4Au/cf4tOvd7Hgay/k//nhWv7xwVoS4mLo3LgGPVt4p+s7N1HIi0jloFCPRt+fjn8Msh+MzOn4gr2wYBzMewoK9sBpF0HW3SxauJBuG56Gly/zRtw7536ISwhLCTWqJTCwbT0GBkJ+z/7DLFzvBfz8r3fy5Edr+eeHa0mIjaFTk+Mhn8YZTWoq5EXElxTq0SomFvreAU17wRuB0/HnPQJnXBve0/EH8+DT8d6tZw98B5nnQ9bdUL8DAPuStsNNM2HGfV6br2fB5S9A7VbhqykgtVo8A06vy4DT6wKw58BhPguE/IKvd/HUR2t54kO8kG9cg54t0ugROJLX1fUi4gf6Sxbtmp39w+n4t37jnY6/4PHQn44/tA8+fRbmPgH7d0LrwV6YN+j807YJ1bwaWvaHqbfCM328r+N1urpcP/9PrRpP/9Pq0v80L+T3Fnghf/wz+adm5vDERznExhht6iXTuUkNzmhSkzOa1KRprWpYRbhWQUTkJCjU/SAp3TsdP+dRmPkgbP4cfjYB6rUr+7oP7YfPXoBP/gH7tkOrAZB1LzTqUvqyp13ghf6Um+G/t3hj4l/wD+97+BGQkhjPOW3qck4bL+TzCg7z2frvWLzRe0xZvImX528EIK16Ap0b1+CMpjXp3LgGHRvX0O1lRaTC018pv4iJgT53eFfHv/FLeK4/nPcwnHHdqR0dHy6ARRNgzmOQ/y20yPLCvEmPk1tPakO49r/efwo++ivkfubdi75Jz5OvKcSSE+Pp16YO/drUAeDoMcfabXks3rCbzwNB/+GqbQDEGLSum0y9+IPsSM6lc5MatKhdXUfzIlKhKNT95olrwpIAABXYSURBVPjp+Ckj4K3fFjodnxzc8kcOwuKXvO/E522BZr29o/6mZ556TTGx0PsP0Lyv93W8f50Hfe+C3rdXqLvTeafhU2hTL4WrejQBvCvsl3yzm8UbvaBf8HUe2f+3FIAa1eLp1PiHU/YdG6eSnBgfyS6ISCVXcf6iSugkpcPVb3pH2TP/Gjgd/+KJT8cfOQRLXoZZj8LeXO+I/7Lx3oh2odKoK9w827v/fPZDsC7b20aNJqHbRojVqJZAVmYdsjK9o/mPZs6k0eldvSP5DbtZvPE7sldvB7wTIq3r/PDZfMfGNWiRXp342JhIdkFEKhGFul/FxECf238YrKak0/FHD8PS1+Dj0bBnIzTqDhc/5Z1uD8ep5cQUL8hb9od3/gBPnw0X/RPaXhr6bYVBjBmt6ybTum4yv+jm/Wdkz4HDLP1mN59v9EJ+2rItTFz4DQDxsUbL9CTa1EumTf0UMuslc1q9FOqmVNGpexEJOYW63zU7yzs6Lno6Pq4qfPE6zHrEuyNcwy7e9Fb9y+cK9Y6/gMbdvJvV/N/1kPOh95+OhOrh33aIpVaNp0/rdPq09ka+O3bMsW5HPss37WXV1jxWbd3Lgq938Z8lm3+0jBfwyWTWS6FN/WQy6ybrYjwRKZNS/4KY2QvABcA259xPzt+ad7jxT2AIsB+43jm3ODBvcGBeLPCcc+5vIaxdglX0dPymxYCDXeugfke4ahJkDCz/4WbTWsCN73mn4mc/BhvnweXPe3eni2IxMUarOsm0qvPj6xj27D/Mqq17Wf1tHiu35LF6617eWJTLvkNHv2/TOK1q4HN973a1beql0KxWNeJ0Cl9EghDMYcEE4CngpRLmnwdkBB49gKeBHmYWC4wBzgVygYVmNtU592VZi5ZTUPh0/OSboGoaDH0VModEduz42Hjof793un/yCHhuAAx4AHr+2qvZR1KrxdOjRS16tKj1/bRjxxybdh/wjui37GXVt97zhyu/5Zjz2iTExdC6bhKZdX8I+4y6SdRLSdQpfBH5kVJD3Tk3y8yanaDJxcBLzjkHzDezGmZWH2gG5Djn1gGY2cRAW4V6JDU7C25bBhZTMW4Ec1zzPvCrufDfW73R6L76yBs/PrlupCsLq5gYo3FaNRqnVePc03/oa8Hho+Rsy2fVVu+IftXWPGat3c6bi3O/b1M9IZYW6Um0qpNEy/TqtAy8blqrOglx/voPkYgEJxQf4DUEvin0PjcwrbjpJ/klZwmLmAo67nm1NBj6ijfYzXv3/nBjmIxzI11ZuUuMj6Vdw1TaNUz90fRd+w6xautevtq+j6+25fPV9nwWrNvJlM83fd8mNsZoklaNlulJtKzzQ9i3TE8itaq+cifiZ+YdYJfSyDtSf7uEz9TfAR5yzs0JvP8QuBNoAQxyzg0PTB8GdHfOjSphGyOAEQDp6eldJk2adCr9qfDy8/NJSorQHdXKQaj6V23fRk7/8u8k7dtAbsML+arldbiYyAdSRd1/BUccW/cdY/M+x5Z9x9iSf4wt+47x7T7HkUK/4ikJRoMko371GOpXj/n+dc1Eo/qBrewtOEJ8WuPIdSTMKur+CwU/9w38379+/fotcs51Let6QnGkngsU/ivQCNgMJJQwvVjOufHAeIDMzEyXlZUVgtIqnuzsbPzaNwhx/wb+HN6/n0afPkOjI+vhgsegcffQrPsURdv+O3L0GLnfHSAncFT/1fZ8crbls2hbPnu/OQQ4utpqRia8Szf7jE9Th9DzslcjXXbYRNv+Oxl+7hv4v3+hEopQnwrcGvjMvAewxzm3xcy2Axlm1hzYBAwFrgrB9qSyiE/0bgTT8hzvZjXPn+t9n73/nyCteaSriwpxsTE0q12dZrWrM4AfPrN3Rw+T9/kUYuY/RdKOpeyPTeGt6lextmYWkR/AV0ROVTBfaXsNyAJqm1ku8CcgHsA5Nw6Yhvd1thy8r7TdEJh3xMxuBd7D+0rbC865FWHog/hd5mBotti7levcJ2Dl29DjZu9q/qo1I11ddDmYB4tfwuaPI2XPRkhrCec/SrWOV3FxQjWys7MjXaGIlEEwV79fWcp8B9xSwrxpeKEvUjZVkqDfPdDlOu+79vPGwOcve2PIdxsOcQmRrrBi25MLC8bBohfh4F5ociac9zdofZ7vvjooUplp+CqJLikN4OIx0ONXMOOP8N498Ol4OPfPcNpFFetrehXB5iUw7ylYMQWcg9MvhjNv9UYQFBHfUahLdKrXDoZN8YaXnfFHmHQtNO4BA//qDT9bmR07BmtneGG+fjYkJEP3m6HnyAp98xwRKTuFukQvM8gY4I1Gt+QV77T88wOg7WUw4E9Qs1mECyxnhw/A0okwfyzsWAMpjWDgX+CMayExtfTlRSTqKdQl+sXGeZ+1t7vcu5Dukydg1dvQfUTluJgufzssfBYWPgf7d0L9Tt4Y+qdf7A3DKyKVhkJd/KNKEvS7F7pcDx8FLqZb8op3MV3XX/rvYrrtq71T7Etfh6MHvYvezrwVmp6lawtEKimFuvhPSgO4ZIz3GfKMP8L0u2HBM9F/Md2RQ3BgF2z7EuY/7X1uHpcIna6CXrdA7YxIVygiEaZQF/+q1x6G/QdyPih0MV1PGPRXaFTm0RhP3ZFDULAbDnwH+3d5zweOPxc3bbc37fC+H9ZRPR2y7oVuv4TqtSPXFxGpUBTq4m9m3g1hWvSDz/8NMx+E5/qX7WK6Y8fgUB4U7PW+8130+fjrgj0/BPP+XV44H/jOW7YkMXHeNQBVa3q3x01pBPU6BN7X8KYl1YVWA7wR90REClGoS+UQGwddb4D2V3gX0s190ruYrsfN3kAsB/MCYbyn+KAu2AsH8zh73y7IPgCUciOkmDjvivOqaV4gJ9eHum1/COuqNbzX1dIKTasJVZKj9+MBEYk4hbpULlWS4Zz7vID/6C8w9ykv4AuLTYAqKZCY8sNzrZaQmMrW7Xtp1Op0b3qV5EJtUn+8THxVhbOIlDuFulROKQ3gkrHeV94O7A6EcnIgkEs+rZ2TnU0j3SlKRCoohbpUbmktIl2BiEjI6E4OIiIiPqFQFxER8QmFuoiIiE8o1EVERHxCoS4iIuITCnURERGfUKiLiIj4hEJdRETEJxTqIiIiPqFQFxER8QmFuoiIiE8o1EVERHxCoS4iIuITQYW6mQ02s9VmlmNmdxcz/w4zWxJ4LDezo2aWFpi33syWBeZ9FuoOiIiIiKfUW6+aWSwwBjgXyAUWmtlU59yXx9s450YDowPtLwR+55zbVWg1/ZxzO0JauYiIiPxIMEfq3YEc59w659whYCJw8QnaXwm8ForiREREJHjBhHpD4JtC73MD037CzKoBg4E3C012wAwzW2RmI061UBERETkxc86duIHZz4BBzrnhgffDgO7OuVHFtP0FcI1z7sJC0xo45zabWR3gfWCUc25WMcuOAEYApKend5k0aVIZulVx5efnk5SUFOkywkb9i27qX/Tyc9/A//3r16/fIudc17Kup9TP1PGOzBsXet8I2FxC26EUOfXunNsceN5mZlPwTuf/JNSdc+OB8QCZmZkuKysriNKiT3Z2Nn7tG6h/0U79i15+7hv4v3+hEszp94VAhpk1N7MEvOCeWrSRmaUCfYH/FppW3cySj78GBgLLQ1G4iIiI/FipR+rOuSNmdivwHhALvOCcW2FmIwPzxwWaXgrMcM7tK7R4XWCKmR3f1qvOuemh7ICIiIh4gjn9jnNuGjCtyLRxRd5PACYUmbYO6FimCkVERCQoGlFORETEJxTqIiIiPqFQFxER8QmFuoiIiE8o1EVERHxCoS4iIuITCnURERGfUKiLiIj4hEJdRETEJxTqIiIiPqFQFxER8QmFuoiIiE8o1EVERHxCoS4iIuITCnURERGfUKiLiIj4hEJdRETEJxTqIiIiPqFQFxER8QmFuoiIiE8o1EVERHxCoS4iIuITCnURERGfUKiLiIj4hEJdRETEJ4IKdTMbbGarzSzHzO4uZn6Wme0xsyWBx/3BLisiIiKhEVdaAzOLBcYA5wK5wEIzm+qc+7JI09nOuQtOcVkREREpo2CO1LsDOc65dc65Q8BE4OIg11+WZUVEROQkBBPqDYFvCr3PDUwrqpeZLTWzd82s7UkuKyIiImVU6ul3wIqZ5oq8Xww0dc7lm9kQ4D9ARpDLehsxGwGMAEhPTyc7OzuI0qJPfn6+b/sG6l+0U/+il5/7Bv7vX6gEE+q5QONC7xsBmws3cM7tLfR6mpmNNbPawSxbaLnxwHiAzMxMl5WVFUz9USc7Oxu/9g3Uv2in/kUvP/cN/N+/UAnm9PtCIMPMmptZAjAUmFq4gZnVMzMLvO4eWO/OYJYVERGR0Cj1SN05d8TMbgXeA2KBF5xzK8xsZGD+OOAK4FdmdgQ4AAx1zjmg2GXD1BcREZFKLZjT7zjnpgHTikwbV+j1U8BTwS4rIiIioacR5URERHxCoS4iIuITCnURERGfUKiLiIj4hEJdRETEJxTqIiIiPqFQFxER8QmFuoiIiE8o1EVERHxCoS4iIuITCnURERGfUKiLiIj4hEJdRETEJxTqIiIiPqFQFxER8QmFuoiIiE8o1EVERHxCoS4iIuITCnURERGfUKiLiIj4hEJdRETEJxTqIiIiPqFQFxER8QmFuoiIiE8o1EVERHwiqFA3s8FmttrMcszs7mLmX21mXwQec82sY6F5681smZktMbPPQlm8iIiI/CCutAZmFguMAc4FcoGFZjbVOfdloWZfA32dc9+Z2XnAeKBHofn9nHM7Qli3iIiIFBHMkXp3IMc5t845dwiYCFxcuIFzbq5z7rvA2/lAo9CWKSIiIqUJJtQbAt8Uep8bmFaSXwLvFnrvgBlmtsjMRpx8iSIiIhIMc86duIHZz4BBzrnhgffDgO7OuVHFtO0HjAXOds7tDExr4JzbbGZ1gPeBUc65WcUsOwIYAZCent5l0qRJZetZBZWfn09SUlKkywgb9S+6qX/Ry899A//3r1+/foucc13Lup5SP1PHOzJvXOh9I2Bz0UZm1gF4DjjveKADOOc2B563mdkUvNP5Pwl159x4vM/iyczMdFlZWcH3IopkZ2fj176B+hft1L/o5ee+gf/7FyrBnH5fCGSYWXMzSwCGAlMLNzCzJsBkYJhzbk2h6dXNLPn4a2AgsDxUxYuIiMgPSj1Sd84dMbNbgfeAWOAF59wKMxsZmD8OuB+oBYw1M4AjgdMIdYEpgWlxwKvOuelh6YmIiEglF8zpd5xz04BpRaaNK/R6ODC8mOXWAR2LThcREZHQ04hyIiIiPqFQFxER8QmFuoiIiE8o1EVERHxCoS4iIuITCnURERGfUKiLiIj4hEJdRETEJxTqIiIiPqFQFxER8QmFuoiIiE8o1EVERHxCoS4iIuITCnURERGfUKiLiIj4hEJdRETEJxTqIiIiPqFQFxER8QmFuoiIiE8o1EVERHxCoS4iIuITCnURERGfUKiLiIj4hEJdRETEJxTqIiIiPhFUqJvZYDNbbWY5ZnZ3MfPNzJ4IzP/CzM4IdlkREREJjVJD3cxigTHAecDpwJVmdnqRZucBGYHHCODpk1hWREREQiCYI/XuQI5zbp1z7hAwEbi4SJuLgZecZz5Qw8zqB7msiIiIhEAwod4Q+KbQ+9zAtGDaBLOsiIiIhEBcEG2smGkuyDbBLOutwGwE3ql7gINmtjyI2qJRbWBHpIsII/Uvuql/0cvPfQP/9y8zFCsJJtRzgcaF3jcCNgfZJiGIZQFwzo0HxgOY2WfOua5B1BZ1/Nw3UP+infoXvfzcN6gc/QvFeoI5/b4QyDCz5maWAAwFphZpMxW4NnAVfE9gj3NuS5DLioiISAiUeqTunDtiZrcC7wGxwAvOuRVmNjIwfxwwDRgC5AD7gRtOtGxYeiIiIlLJBXP6HefcNLzgLjxtXKHXDrgl2GWDMP4k20cTP/cN1L9op/5FLz/3DdS/oJiXxyIiIhLtNEysiIiIT0Qs1Msy9GxFZ2aNzWymma00sxVm9tti2mSZ2R4zWxJ43B+JWk+Vma03s2WB2n9y1WaU77/MQvtliZntNbPbirSJqv1nZi+Y2bbCXxU1szQze9/M1gaea5awbIUf6rmE/o02s1WBn78pZlajhGVP+LMcaSX07QEz21To529ICctG6757vVDf1pvZkhKWrdD7DkrOg7D9/jnnyv2Bd9HcV0ALvK+9LQVOL9JmCPAu3nfdewILIlHrKfavPnBG4HUysKaY/mUBb0e61jL0cT1Q+wTzo3b/FelHLLAVaBrN+w/oA5wBLC807RHg7sDru4GHS+j/CX9XK8KjhP4NBOICrx8urn+BeSf8WY70o4S+PQDcXspyUbvvisx/FLg/GvddoMZi8yBcv3+ROlIvy9CzFZ5zbotzbnHgdR6wkso3kl7U7r8i+gNfOec2RLqQsnDOzQJ2FZl8MfBi4PWLwCXFLBoVQz0X1z/n3Azn3JHA2/l442REnRL2XTCidt8dZ2YG/Bx4rVyLCqET5EFYfv8iFeplGXo2qphZM6AzsKCY2b3MbKmZvWtmbcu1sLJzwAwzW2TeaIBF+WL/4Y2tUNIflGjefwB1nTeeBIHnOsW08ct+vBHvzFFxSvtZrqhuDXy08EIJp279sO96A98659aWMD+q9l2RPAjL71+kQr0sQ89GDTNLAt4EbnPO7S0yezHeKd2OwJPAf8q7vjI6yzl3Bt4d+G4xsz5F5vth/yUAFwH/V8zsaN9/wfLDfrwPOAK8UkKT0n6WK6KngZZAJ2AL3inqoqJ+3wFXcuKj9KjZd6XkQYmLFTPthPswUqFelqFno4KZxePtwFecc5OLznfO7XXO5QdeTwPizax2OZd5ypxzmwPP24ApeKeJCovq/RdwHrDYOfdt0RnRvv8Cvj3+kUjgeVsxbaJ6P5rZdcAFwNUu8CFlUUH8LFc4zrlvnXNHnXPHgGcpvuZo33dxwGXA6yW1iZZ9V0IehOX3L1KhXpahZyu8wOdAzwMrnXOPldCmXqAdZtYdb1/sLL8qT52ZVTez5OOv8S5IKnoDnqjdf4WUeJQQzfuvkKnAdYHX1wH/LaZN1A71bGaDgbuAi5xz+0toE8zPcoVT5PqUSym+5qjddwEDgFXOudziZkbLvjtBHoTn9y+CVwQOwbsK8CvgvsC0kcDIwGsDxgTmLwO6RqrWU+jb2XinSL4AlgQeQ4r071ZgBd7VjPOBMyNd90n0r0Wg7qWBPvhq/wXqr4YX0qmFpkXt/sP7z8kW4DDe//5/CdQCPgTWBp7TAm0bANMKLfuT39WK9iihfzl4n0ce/x0cV7R/Jf0sV6RHCX37d+D36gu8P/L1/bTvAtMnHP99K9Q2qvZdoM6S8iAsv38aUU5ERMQnNKKciIiITyjURUREfEKhLiIi4hMKdREREZ9QqIuIiPiEQl1ERMQnFOoiIiI+oVAXERHxif8P1fKz0fhkFkIAAAAASUVORK5CYII=\n",
      "text/plain": [
       "<Figure size 576x360 with 1 Axes>"
      ]
     },
     "metadata": {
      "needs_background": "light"
     },
     "output_type": "display_data"
    }
   ],
   "source": [
    "def plot_learning_curves(history, label, epochs, min_value, max_value):\n",
    "    data = {}\n",
    "    data[label] = history.history[label]\n",
    "    data['val_'+label] = history.history['val_'+label]\n",
    "    pd.DataFrame(data).plot(figsize=(8, 5))\n",
    "    plt.grid(True)\n",
    "    plt.axis([0, epochs, min_value, max_value])\n",
    "    plt.show()\n",
    "    \n",
    "plot_learning_curves(history, 'accuracy', epochs, 0, 1)\n",
    "plot_learning_curves(history, 'loss', epochs, 0, 2)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 模型预测"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Found 300000 validated image filenames belonging to 10 classes.\n",
      "300000\n"
     ]
    }
   ],
   "source": [
    "test_datagen = keras.preprocessing.image.ImageDataGenerator(\n",
    "    rescale= 1./255, # 缩放到0-1之间\n",
    ")\n",
    "test_generator = test_datagen.flow_from_dataframe(\n",
    "    test_df,\n",
    "    directory = './',\n",
    "    x_col = 'filepath',\n",
    "    y_col = 'class',\n",
    "    classes = class_names,\n",
    "    target_size = (height, width),\n",
    "    batch_size = batch_size,\n",
    "    seed = 7,\n",
    "    shuffle = False,\n",
    "    class_mode = 'sparse'\n",
    ")\n",
    "\n",
    "test_num = test_generator.samples\n",
    "print(test_num)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "metadata": {},
   "outputs": [],
   "source": [
    "# 预测\n",
    "test_predict = model.predict_generator(\n",
    "    test_generator,\n",
    "    workers = 4,\n",
    "#     use_multiprocessing = True\n",
    ")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "(300000, 10)\n"
     ]
    }
   ],
   "source": [
    "print(test_predict.shape)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[[1.19752169e-01 1.97736397e-02 1.87746808e-01 1.21884443e-01\n",
      "  2.00869158e-01 8.07953775e-02 4.16674986e-02 7.32287019e-02\n",
      "  5.77242374e-02 9.65579823e-02]\n",
      " [5.95469415e-01 2.41348259e-02 2.68329252e-02 8.76767468e-03\n",
      "  1.09298825e-02 1.09132123e-03 9.06108599e-03 1.71968562e-03\n",
      "  1.68620404e-02 3.05131167e-01]\n",
      " [3.27107002e-04 3.34682465e-02 2.92815685e-07 6.75953004e-07\n",
      "  4.28483204e-08 1.66831065e-08 2.87153199e-07 6.09965764e-06\n",
      "  3.20274485e-05 9.66165245e-01]\n",
      " [2.52128318e-02 9.88447806e-04 1.86447520e-04 1.03312828e-04\n",
      "  1.09217326e-04 5.01413160e-06 3.08552371e-05 1.21058511e-05\n",
      "  9.70176160e-01 3.17557761e-03]\n",
      " [7.14224756e-01 8.79984582e-05 2.10715935e-01 9.21514910e-03\n",
      "  2.38626227e-02 1.45898783e-03 6.03097142e-04 2.24490347e-03\n",
      "  3.69998068e-02 5.86773618e-04]]\n"
     ]
    }
   ],
   "source": [
    "print(test_predict[0:5])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "metadata": {},
   "outputs": [],
   "source": [
    "# 获取预测的索引\n",
    "test_predict_class_indices = np.argmax(test_predict, axis = 1)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[4 0 9 8 0]\n"
     ]
    }
   ],
   "source": [
    "print(test_predict_class_indices[0:5])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 18,
   "metadata": {},
   "outputs": [],
   "source": [
    "# 获取预测的类别名称\n",
    "test_predict_class = [class_names[index] for index in test_predict_class_indices]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 19,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "['deer', 'airplane', 'truck', 'ship', 'airplane']\n"
     ]
    }
   ],
   "source": [
    "print(test_predict_class[0:5])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 20,
   "metadata": {},
   "outputs": [],
   "source": [
    "# 生成submission文件\n",
    "def generate_submissions(filename, predict_class):\n",
    "    with open(filename, 'w') as f:\n",
    "        f.write('id,label\\n')\n",
    "        for i in range(len(predict_class)):\n",
    "            f.write('%d,%s\\n' % (i+1, predict_class[i]))\n",
    "            \n",
    "output_file = './data/cifar10/submission.csv'\n",
    "generate_submissions(output_file, test_predict_class)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Describe Submission:\n",
    "\n",
    "train 45000,20 epochs,basic model with only conv + bn + pooling"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.6.10"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
